1. Field of the Invention
This invention relates generally to object-oriented design patterns for creating reusable metaclass types and specifically to an intelligent creator class for instantiating polymorphic subclasses of a metaclass that are unknown to the object-oriented application.
2. Description of the Related Art
Object-oriented technology employs an "object model" encompassing the well-known principles of abstraction, encapsulation, modularity, hierarchy, typing, concurrency, and persistence. Although none of these principles is by itself new, all of these elements are brought together synergistically to make up the "object model" underlying object-oriented technology. For a fundamental treatment of object-oriented analysis and design, reference is made to Grady Booch, Object-Oriented Analysis and Design with Applications, 2nd ed., The Benjamin/Cummings Publishing Co., Redwood City, Calif., 1994.
According to Booch, "abstraction" denotes the essential characteristics of an object that distinguishes it from all other kinds of objects, thereby providing crisply-defined conceptual boundaries relative to a viewer perspective. The abstraction property delivers "reusability" by means of encapsulation or information-hiding. Encapsulation is the process of compartmentalizing the elements of an abstraction that constitute its structure and behavior, serving to separate the contractual interface of an abstraction and its implementation by declaring publicly the specification of their behavior through a set of attributes and operations while keeping private to the object all implementing data structures and methods. Object types or classes are similar to data types and entity types with encapsulated methods or procedures. Data and methods are encapsulated and hidden by objects, which constitute concrete "instances" of a class. The terms "object" and "instance" are used interchangeably in the art. "Hierarchy" is a ranking or ordering of abstractions. "Inheritance" is a relationship among classes, wherein one class shares the structure or behavior defined in one or more other classes. Subclasses inherit attributes and methods from their superclasses and may add others of their own or override those inherited. In many object-oriented programming languages, "instances" inherit all and only the properties of their respective "classes." Inheritance defines an "is-a" hierarchy among classes in which a subclass inherits from one or more generalized superclasses; a subclass typically specializes its superclasses by augmenting or redefining their existing structure and behavior. A "metaclass" is a class of classes, instances of which are themselves classes.
An "abstract" class has no "instances" because it is written in the expectation that its concrete subclasses will add to its structure and behavior, typically by implementing its abstract operations. An abstract operation is declared but not implemented by an abstract class. A "base class" is the most generalized class in a class structure and may, in some situations, define the ultimate superclass of all classes.
"Typing" is the enforcement of the class of an object so that objects of different types may not be interchanged or may be interchanged only in very restricted ways. An object "type" defines a domain of allowable values that the object may possess and the set of operations that may be performed upon the object. The terms "class" and "type" are generally used interchangeably in the art but are not entirely synonymous. Object-oriented systems can include "strong typing," wherein type or class conformance is strictly enforced, or "weak typing," wherein conformance is weakly enforced. A strongly-typed system introduces semantic dependencies such that even small changes in a base class interface require recompilation of all subclasses. The related concepts of "static binding" and "dynamic binding" refer to the time when names are bound to types or classes. Static binding means that the types of all variables and expressions are fixed at the time of compilation; dynamic binding means that the types of all variables and expressions are not known until runtime.
The distinction between an object's "class" and its "type" is subtle but important. An object's class defines how the object is implemented. The class defines the object's internal state and the implementation of its operations. In contrast, an object's type only refers to its interface, which is the set of requests to which the object can respond. An object can have many types and objects of different classes can have the same type. Because a class defines the operations that an object can perform, it also defines the object's type. When an object is an instance of a class, it is implied in the art that the object supports the interface defined by the class.
"Polymorphism" represents a concept in type theory in which a single name (such as a variable declaration) may denote objects of many different classes that are related by some common superclass. Any object denoted by this name can therefore respond to some common set of operations. Polymorphism exists when the features of inheritance and dynamic binding interact and is perhaps one of the two most powerful features of object-oriented programming systems.
All well-structured object-oriented system architectures are based on standard patterns for class designs. Reference is made to Erich Gamma et al., (Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley Publishing Co., Reading, Mass., 1995) for a description of the important design patterns known in the object-oriented programming arts. Gamma et al. describe a catalog of 23 design patterns for classes suitable for use in object-oriented systems.
According to Gamma et al., class inheritance is fundamentally a mechanism for extending an application's functionality through the reuse of the parent class functionality. Class inheritance permits rapid definition of a new kind of object in terms of an old one, providing new implementations for merely the cost of changes made to an existing class. Class inheritance also supports polymorphism by providing for the definition of families of objects with identical interfaces, usually through inheritance from one base class. When inheritance is used properly, all subclasses derived from a base class must share its interface. This implies that a subclass merely adds or overrides operations and does not hide operations of the parent class. All subclasses may then respond to the requests in the base class interface, making them all subtypes of the base class.
Practitioners in the art recognize that class inheritance has some disadvantages as well. First, there are no means for changing the implementations inherited from parent classes at runtime because inheritance is defined at time of compilation. Second, and more importantly, parent classes often define at least part of their subclasses' physical representation. The implementation of a subclass then becomes so bound up with the parent class implementation that any change in the parent's implementation must force the subclass to change. This exposure of the subclass to the details of its parent's implementation is said in the art to lead to "inheritance breaking encapsulation."
All object-oriented programming systems are characterized by a tension and opposition between the two key features of "encapsulation" and "inheritance." The use of polymorphism increases readability and programmer productivity but may lead to slower software implementations. Accordingly, practitioners in the art strive to balance the mutually inconsistent properties of reusability, extensibility, and polymorphism in object-oriented programming systems to optimize overall system performance.
Implementation dependencies can create problems when attempting to reuse a subclass. Whenever any aspect of the inherited implementation is not appropriate for the new problem domain, the base class must be rewritten or replaced by something more appropriate. This dependency limits flexibility and ultimately reusability. One cure for this is to inherit only from abstract classes because they usually provide little or no implementation. Favoring object composition over class inheritance helps to keep each class encapsulated and focused on a single task. The concept of "delegation" offers a way to make composition as powerful for reuse as inheritance. With delegation, two objects are involved in handling a request: a receiving object delegates operations to its "delegate," with is analogous to subclasses deferring requests to parent classes. Delegation is an extreme example of using object composition to replace inheritance as a mechanism for ensuring code reusability without rewriting existing classes.
Less extremely, application code is often written to contain sets of objects that inherit from an abstract base class so that the application can deal with the different subclasses of the abstract base class through the protocol established by the abstract base class. This allows most of the application to be written without knowledge of the details of the different subclasses or even without knowledge of the existence of the several subclasses. Disadvantageously, even with this strategy, there must always be some code somewhere in the application that actually instantiates the subclasses. Preparation of this code requires knowledge of all of the possible subclasses to permit selection of the proper subclass for instantiation. Accordingly, whenever a new subclass is added to the application, system developers must rewrite at least this portion of the application code to incorporate knowledge of each new subclass as it is added to the system.
This requirement to rewrite existing class code means that the entire application must be recompiled whenever a new subclass is added. This situation, well-known in the art, hampers the flexibility of the application and gives rise to a clearly-felt need in the art for some mechanism that allows addition of new application subclasses of a given base class without modification to any existing application code.
Gamma et al. describes several design patterns that attempt to limit this problem by isolating much of the application from the effects of creating new subclasses but the best of these design patterns merely isolate the main architectural elements of the application and do not eliminate all changes to existing code. For instance, as mentioned above, the application code that lists the subclasses available for instantiation must always be modified by the programmer when adding a subclass. None of the solutions offered by Gamma et al. can eliminate the application's requirement to know which new classes are added to the application. Without an available solution to this well-known problem, practitioners in the art continue to be obliged to incur full application recompilation to add a single new subclass in any application. These unresolved problems and deficiencies are clearly felt in the art and are solved by this invention in the manner described below.